const nsIDownloadBinning = Components.interfaces.nsIDownloadBinning;
const nsISupports = Components.interfaces.nsISupports;
const CLASS_ID = Components.ID("{9D720080-B8B9-11DD-AE0E-7CBB55D89593}");
const CLASS_NAME = "JavaScript XPCOM Component for Download Binning";
const CONTRACT_ID = "@downloadbinning.net/main;1";

const prefix_db_preference = "extensions.downloadbinning";

// constructor
function DownloadBinning() {
  //initialize data properties
  this.pref = Components.classes["@mozilla.org/preferences-service;1"].getService(Components.interfaces.nsIPrefBranch);
  this.consoleService = Components.classes["@mozilla.org/consoleservice;1"].getService(Components.interfaces.nsIConsoleService);
/*
  var prefservice = Components.classes["@mozilla.org/preferences-service;1"].getService(Components.interfaces.nsIPrefService);
  this.branch = prefservice.getBranch(prefix_db_preference);
  this.branch.QueryInterface(Components.interfaces.nsIPrefBranch2);
  this.register();
*/
  this.rules = this.loadRulesFromPref();
}

DownloadBinning.prototype = {
  pref: null,
  consoleService: null,
  branch: null,
  rules: null,

  QueryInterface: function(aIID) {
    if (!aIID.equals(nsIDownloadBinning) &&    
        !aIID.equals(nsISupports))
      throw Components.results.NS_ERROR_NO_INTERFACE;
    return this;
  },

  // return
  //   Basic mode: 0 (zero)
  //   Advanced mode: non-zero number
  getDefaultMode: function() {
    var mode;
    try {
      mode = this.pref.getIntPref(prefix_db_preference + ".defaultMode");
    } catch (e) {
      this.log("[getDefaultMode] Fresh Install");
      mode = 0;
  		this.pref.setIntPref(prefix_db_preference + ".defaultMode", 0);
    }
    return mode;
  },

  setDefaultMode: function(mode) {
		this.pref.setIntPref(prefix_db_preference + ".defaultMode", mode);
  },

  //int addRule(rule);
  //Return: index of the rule
  addRule: function(rule) {
    if (this.rules == null) this.rules = new Array();
    new_idx = this.rules.length;
    this.rules[new_idx] = rule;
    this.updateRuleInPref(new_idx, rule);
    return new_idx;
  },

  //boolean updateRule(index, rule);
  //Return: False if failed
  updateRule: function(index, rule) {
    if (this.rules == null || index < 0 || index >= this.rules.length) return false;
    
    this.rules[index] = rule;
    this.updateRuleInPref(index, rule);
    return true;
  },

  //nsIDownloadBinningRule getRule(index)
  getRule: function(index) {
    if (this.rules == null || index < 0 || index >= this.rules.length) return null;
    return this.rules[index];
  },

  //boolean removeRule(index)
  //Return: False if failed
  removeRule: function(index) {
    this.log("[removeRule]");

    if (this.rules == null || index < 0 || index >= this.rules.length) return false;
    this.rules.splice(index, 1);
    
    // update broswer preference
    this.deleteRuleInPref(this.rules.length);
    for (var i = index; i < this.rules.length; i++) this.updateRuleInPref(i, this.rules[i]);  
    
    return true;
  },

  //nsIDownloadBinningRule[] getRules()
  //Return: a set of rules
  getRules: function(count) {
  	count.value = this.rules.length;
    this.log("[getRules] Total Rules: " + this.rules.length);
  	return this.rules;
  },
  
  //nsIDownloadBinningRule[] getEnabledRules()
  //Return: a set of enabled rules
  getEnabledRules: function(count) {
    if (this.rules == null) return null;
    
    var enabled_rules = new Array();
    var num = 0;
    for (var i = 0; i < this.rules.length; i++) {
      if (this.rules[i].getEnabled() == true) {
        enabled_rules[num++] = this.rules[i];
      }
    }
    
    count.value = enabled_rules.length;
    return enabled_rules;
  },
  
  moveUp: function(index) {
    if (index <= 0 || index >= this.rules.length) return false;
    var tmp = this.rules[index];
    this.rules[index] = this.rules[index - 1];
    this.rules[index - 1] = tmp;
    
    this.updateRuleInPref(index, this.rules[index]);
    this.updateRuleInPref(index - 1, this.rules[index - 1]);
    
    //this.updateRuleInPref(index, this.rules[index - 1]);
    //this.updateRuleInPref(index - 1, tmp);
    return true;
  },
  
  moveDown: function(index) {
    if (index < 0 || index + 1 >= this.rules.length) return false;
    var tmp = this.rules[index];
    this.rules[index] = this.rules[index + 1];
    this.rules[index + 1] = tmp;

    this.updateRuleInPref(index, this.rules[index]);
    this.updateRuleInPref(index + 1, this.rules[index + 1]);
    
    //this.updateRuleInPref(index, this.rules[index + 1]);
    //this.updateRuleInPref(index + 1, tmp);
    return true;
  },
  
  loadRulesFromPref: function() {
    this.log("[loadRulesFromPref]");
    try {
      var n = this.pref.getIntPref(prefix_db_preference + ".rules.total");
      
      this.rules = new Array();
    	for (var i = 0; i < n; i++) {
    		var name = this.pref.getCharPref(prefix_db_preference + ".rules." + i + ".name");
    		var desc = this.pref.getCharPref(prefix_db_preference + ".rules." + i + ".desc");
    		var domain = this.pref.getCharPref(prefix_db_preference + ".rules." + i + ".domain");
    		var filename = this.pref.getCharPref(prefix_db_preference + ".rules." + i + ".filename");		
    		var destination = this.pref.getCharPref(prefix_db_preference + ".rules." + i + ".destination");		
    		var enabled = this.pref.getBoolPref(prefix_db_preference + ".rules." + i + ".enabled");	
        
        var new_rule = Components.classes["@downloadbinning.net/rule;1"].createInstance(Components.interfaces.nsIDownloadBinningRule);
        new_rule.setName(name);
        new_rule.setDescription(desc);
        if (this.isRegEx(domain)) {
          new_rule.setDomain(domain.substring(1, domain.length - 1), true);
        } else {
          new_rule.setDomain(domain, false);
        }
        if (this.isRegEx(filename)) {
          new_rule.setFilename(filename.substring(1, filename.length - 1), true);
        } else {
          new_rule.setFilename(filename, false);
        }
        new_rule.setDestination(destination);
        new_rule.setEnabled(enabled);
    		this.rules[i] = new_rule;
    	}	
      
    } catch (e) {
      //this.log(e);
      // fresh install
      this.log("[loadRulesFromPref] Fresh Install");
  		this.pref.setIntPref(prefix_db_preference + ".rules.total", 0);
      this.rules = new Array();
    }
    
    this.log("[loadRulesFromPref] Done: Total Rules: " + this.rules.length);
    return this.rules;
  },

  updateRuleInPref: function(index, rule) {
    if (index >= this.rules.length) return;

    this.pref.setCharPref(prefix_db_preference + ".rules." + index + ".name", rule.getName());
    this.pref.setCharPref(prefix_db_preference + ".rules." + index + ".desc", rule.getDescription());
    this.pref.setCharPref(prefix_db_preference + ".rules." + index + ".domain", rule.getDomainInRaw());
    this.pref.setCharPref(prefix_db_preference + ".rules." + index + ".filename", rule.getFilenameInRaw());
    this.pref.setCharPref(prefix_db_preference + ".rules." + index + ".destination", rule.getDestination());
    this.pref.setBoolPref(prefix_db_preference + ".rules." + index + ".enabled", rule.getEnabled());

    this.pref.setIntPref(prefix_db_preference + ".rules.total", this.rules.length);
  },
  
  deleteRuleInPref: function(index) {
    this.pref.deleteBranch(prefix_db_preference + ".rules." + index);
    this.pref.setIntPref(prefix_db_preference + ".rules.total", this.rules.length);
  },

  getLocation: function(domain, filename) {
    if (domain == null || filename == null) return null;
    
    var match_rule = -1;
    for (var i = 0; i < this.rules.length; i++) {
      if (this.rules[i].getEnabled()) {

        var r_domain = this.rules[i].getDomainInRegEx();
        var r_filename = this.rules[i].getFilenameInRegEx();
        
        this.log("Domain: " + r_domain + ", Filename: " + r_filename);
        
        // check domain
        if (!this.match(r_domain, domain)) continue;
        // check filename
        if (!this.match(r_filename, filename)) continue;
        
        this.log("Match: Destination " + this.rules[i].getDestination());
        return this.rules[i].getDestination();
      }
    }
    
    return null;
  },
  
  match: function(regex_str, string) {
    this.log("match function: regex_str = " + regex_str + " with " + string);
    var regex_obj = new RegExp(regex_str, "gi");
    return string.match(regex_obj);
  },

  isRegEx: function(regex) {
    return (regex.substring(0,1) == "/") && (regex.substr(regex.length - 1, 1) == "/");
  },
  
  isValidWildcard: function(wildcard) {
    // implement this
    return true;
  },

/*  
  register: function() {
    this.branch.addObserver("", this, false);
  },

  unregister: function() {
    if (this.branch) this.branch.removeObserver("", this);
  },

  observe: function(subject, topic, data) {
    this.log("observer: topic = " + topic);
    if (topic == "nsPref:changed") this.onPrefChange(this.branch, data);
  },

  onPrefChange: function(branch, name) {
    //this.log("name: " + name);

    if (name == ".rules.total") {
      var total = this.pref.getIntPref(prefix_db_preference + ".rules.total");
      this.log("total = " + total);
      if (total < 0) {
        this.pref.setIntPref(prefix_db_preference + ".rules.total", 0);
      } else if (this.rules != null) {
        this.log("total rules (in memory) = " + this.rules.length);
      
        if (total > this.rules.length) {
          this.pref.setIntPref(prefix_db_preference + ".rules.total", this.rules.length);
        } else {
          var current_size = this.rules.length;
          this.rules.splice(total, current_size - total);
        }
      }
      return;
    }
    
    var tokens = name.split(".");
    var index = parseInt(tokens[2]);
    var field = tokens[3];
    this.log("index: " + index + ", field: " + field);

    // create a new instance if new rule is being created
    if (index >= this.rules.length) {
      this.log("creating new rule");
      this.pref.setIntPref(prefix_db_preference + ".rules.total", index);
      this.rules[index] = Components.classes["@downloadbinning.net/rule;1"].createInstance(Components.interfaces.nsIDownloadBinningRule);
    }
    
    switch (field) {
      case "name":
        this.rules[index].setName(this.pref.getCharPref(prefix_db_preference + ".rules." + index + ".name"));
        break;
      case "desc":
        this.rules[index].setDescription(this.pref.getCharPref(prefix_db_preference + ".rules." + index + ".desc"));
        break;
      case "domain":
        this.rules[index].setDomain(this.pref.getCharPref(prefix_db_preference + ".rules." + index + ".domain"));
        break;
      case "filename":
        this.rules[index].setFilename(this.pref.getCharPref(prefix_db_preference + ".rules." + index + ".filename"));
        break;
      case "destination":
        this.rules[index].setDestination(this.pref.getCharPref(prefix_db_preference + ".rules." + index + ".destination"));
        break;
      case "enabled":
        this.rules[index].setEnabled(this.pref.getBoolPref(prefix_db_preference + ".rules." + index + ".enabled"));
        break;
    }
  },
*/

  log: function(aMessage) {
    //this.consoleService.logStringMessage("DownloadBinning: " + aMessage);
  }
}

var DownloadBinningFactory = {
  createInstance: function (aOuter, aIID) {
    if (aOuter != null)
      throw Components.results.NS_ERROR_NO_AGGREGATION;
    return (new DownloadBinning()).QueryInterface(aIID);
  }
};

var DownloadBinningModule = {
  registerSelf: function(aCompMgr, aFileSpec, aLocation, aType) {
    aCompMgr = aCompMgr.
        QueryInterface(Components.interfaces.nsIComponentRegistrar);
    aCompMgr.registerFactoryLocation(CLASS_ID, CLASS_NAME, 
        CONTRACT_ID, aFileSpec, aLocation, aType);
  },

  unregisterSelf: function(aCompMgr, aLocation, aType) {
    aCompMgr = aCompMgr.
        QueryInterface(Components.interfaces.nsIComponentRegistrar);
    aCompMgr.unregisterFactoryLocation(CLASS_ID, aLocation);        
  },
  
  getClassObject: function(aCompMgr, aCID, aIID) {
    if (!aIID.equals(Components.interfaces.nsIFactory))
      throw Components.results.NS_ERROR_NOT_IMPLEMENTED;

    if (aCID.equals(CLASS_ID))
      return DownloadBinningFactory;

    throw Components.results.NS_ERROR_NO_INTERFACE;
  },

  canUnload: function(aCompMgr) { return true; }
};

function NSGetModule(aCompMgr, aFileSpec) { return DownloadBinningModule; }
